package com.eureka.pay.controller;

import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.eureka.bclass.base.BaseLoginUserInfo;
import com.eureka.bclass.base.BaseResult;
import com.eureka.bclass.util.*;
import com.eureka.pay.configurer.properties.AdvancePaymentProperties;
import com.eureka.pay.configurer.properties.AdvancePaymentPropertiesConfig;
import com.eureka.pay.enums.ClientEnum;
import com.eureka.pay.event.PayEvent;
import com.eureka.pay.util.*;
import com.github.wxpay.sdk.WXPayConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.text.MessageFormat;
import java.util.Map;

/**
 * @Description: 聚合扫码支付（微信、支付宝扫码）控制器
 * @Author: lixing
 * @Date: 2021/7/6 8:45
 */
@Controller
@RequestMapping(("aggregatePay"))
public class AggregatePayController {
    /** 聚合支付地址 */
    @Value("${aggregatePay.scanQRCode}")
    private String aggregatePayScanQRCode;

    /** 如果是微信客户端扫描的，则需要跳转到相关页面通过jsapi唤起微信支付 */
    @Value("${aggregatePay.wxJsapiTransferPaymentPage}")
    private String aggregatePayWxJsapiTransferPaymentPage;

    /** 预支付信息 */
    @Autowired
    private AdvancePaymentPropertiesConfig advancePaymentPropertiesConfig;

    /** 容器上下文对象 */
    @Resource
    private ApplicationContext applicationContext;

    /**
     * @Description 获取聚合支付的二维码
     * @Author lixing
     * @Date 2021/7/6 10:02
     * @Param [businessType 聚合支付的业务类型]
     */
    @GetMapping("getPayQRCode/{businessType}")
    public void getPayQRCode(@PathVariable("businessType") String businessType, HttpServletRequest request, HttpServletResponse response) throws Exception {
        BaseLoginUserInfo baseLoginUserInfo = new BaseLoginUserInfo();
        baseLoginUserInfo.setUserId(31011225L);
                // JwtUtil.verifyJwtForHs256(request.getHeader("Authorization"));
        // 生产支付编号
        long orderNo = 1234567889L;
                // new SnowFlakeShortUrlUtil(baseLoginUserInfo.getUserId(), Long.getLong(businessType)).nextId();
        // 根据businessType生成二维码
        String qrCodeUrl = MessageFormat.format("{0}/{1}/{2}/{3}", aggregatePayScanQRCode, businessType, String.valueOf(baseLoginUserInfo.getUserId()), orderNo);
        //
        OutputStream output = response.getOutputStream();
        QrCodeUtil.encode(qrCodeUrl, null, output, true);
    }

    /**
     * @Description 聚合支付 - 微信、支付宝扫支付聚合二维码后触发
     * @Author lixing
     * @Date 2021/7/6 8:51
     * @Param [request, response, businessType 聚合支付的业务类型, userId 用户id, orderNo 订单编号]
     */
    @GetMapping("scanQRCode/{businessType}/{userId}/{orderNo}")
    public void scanQRCode(HttpServletRequest request, HttpServletResponse response,
            @PathVariable("businessType") String businessType, @PathVariable("userId") long userId, @PathVariable("orderNo") String orderNo) throws Exception {
        if (UserAgentUtil.isAlipayClient(request)) {
            // TODO 支付宝客户端扫码
            arouseAliPay(ClientEnum.ALIBABA.getClient(), businessType, userId, orderNo, response);
        } else if (UserAgentUtil.isWechatClient(request)) {
            // TODO 微信客户端打开扫码
            arouseWechatPay(ClientEnum.WECHAT.getClient(), businessType, userId, orderNo, request, response);
        } else {
            // 扫码终端类型错误
            BaseResult.outputJson(response, new BaseResult<String>().fail("请使用支付宝或微信扫码支付"));
        }
    }

    /**
     * @Description 唤起支付宝支付
     * @Author lixing
     * @Date 2021/7/6 9:39
     * @Param [client 扫码终端类型, businessType 聚合支付的业务类型, userId 用户id, orderNo 订单编号]
     * <p>
     * 唤醒支付宝支付，其实使用的是支付宝手机网站支付(需要签约):
     * 1、一种是页面唤醒，自己构建请求form参数然后请求支付宝唤醒支付操作
     * 2、另一种是服务端调用支付宝的SDK去生成form参数然后去请求支付宝（推荐）
     */
    public void arouseAliPay(String client, String businessType, long userId, String orderNo, HttpServletResponse response) throws UnsupportedEncodingException {
        // 根据 businessType 获取 相关参数
        AdvancePaymentProperties advancePaymentProperties = advancePaymentPropertiesConfig.getParamsByBusinessType(client, businessType, userId);
        advancePaymentProperties.setUserId(userId);
        advancePaymentProperties.setOrderNo(orderNo);
        // SDK 公共请求类，包含公共请求参数，以及封装了签名与验签，开发者无需关注签名与验签
        AlipayClient alipayClient = new DefaultAlipayClient(
                advancePaymentProperties.getAlibabaGatewayUrl(),
                advancePaymentProperties.getAlibabaAppId(),
                advancePaymentProperties.getAlibabaRsaPrivateKey(),
                advancePaymentProperties.getAlibabaFormat(),
                advancePaymentProperties.getAlibabaCharset(),
                advancePaymentProperties.getAlibabaRsaPublicKey(),
                advancePaymentProperties.getAlibabaSignType());
        AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();
        // 封装请求支付信息
        AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
        // 商户订单号，商户网站订单系统中唯一订单号，必填
        model.setOutTradeNo(orderNo);
        // 公共回传参数，本参数必须进行UrlEncode之后才可以发送给支付宝
        model.setPassbackParams(advancePaymentProperties.getPassbackParams());
        // 订单名称，必填
        model.setSubject(advancePaymentProperties.getSubject());
        // 付款金额（单位：元），必填
        model.setTotalAmount(advancePaymentProperties.getTotalAmount());
        // 销售产品码 必填
        model.setProductCode(advancePaymentProperties.getProductId());
        // 商品描述，可空
        model.setBody(advancePaymentProperties.getBody());
        // 超时时间 可空
        model.setTimeoutExpress("2m");
        //
        alipayRequest.setBizModel(model);
        // 设置异步通知地址
        alipayRequest.setNotifyUrl(advancePaymentProperties.getAlibabaNotifyUrl());
        // 设置同步地址。可实现支付成功后跳转到商家页面的功能，而且跳转后的return_url页面的地址栏中会返回同步通知参数
        alipayRequest.setReturnUrl(advancePaymentProperties.getAlibabaReturnUrl());
        /*
         * 调用SDK生成表单
         */
        String form = "";
        PrintWriter printWriter = null;
        try {
            form = alipayClient.pageExecute(alipayRequest).getBody();
            response.setContentType("text/html;charset=UTF-8");
            printWriter = response.getWriter();
            printWriter.write(form);
            printWriter.flush();
        } catch (AlipayApiException | IOException e) {
            e.printStackTrace();
        } finally {
            if (null != printWriter) {
                printWriter.close();
            }
            /*
             * 异步添加预支付记录
             */
            applicationContext.publishEvent(new PayEvent(this, advancePaymentProperties));
        }
    }

    /**
     * @Description 唤起微信支付
     * @Author lixing
     * @Date 2021/7/6 9:39
     * @Param [client 扫码终端类型, businessType 聚合支付的业务类型, userId 用户id, orderNo 订单编号]
     * <p>
     * 唤起微信支付，其实是微信公众号支付：因为调用微信公众号的统一下单API需要传入openID，所以这个过程需要用户先授权登录该公众号获取openId，再发起统一支付
     */
    public void arouseWechatPay(String client, String businessType, long userId, String orderNo, HttpServletRequest request, HttpServletResponse response) throws Exception {
        String openId = ""; // ""oVRIQ47eUZSk2FL9UFweZn1chEYk";
        if (StrUtil.isEffective(openId)) {
            // TODO 本地测试（发起统一支付），生产请注释
            weChatUnifiedorder(client, businessType, openId, userId, orderNo, request, response);
        } else {
            // 校验用户是否登录授权
            String code = checkCode(businessType, userId, request, response);
            if (StrUtil.isEffective(code)) {
                // 获取用户的openId
                String openid = checkAuth(businessType, userId, code);
                if (!StrUtil.isEffective(openid)) {
                    BaseResult.outputJson(response, new BaseResult<String>().fail("发起统一支付失败，请联系管理员"));
                } else {
                    // 发起统一支付
                    weChatUnifiedorder(client, businessType, openid, userId, orderNo, request, response);
                }
            }
        }
    }

    /**
     * @Description 判断code是否存在，不存在则表示该用户还没有授权，需要登录授权后获取code
     * @Author lixing
     * @Date 2021/7/6 16:02
     * @Param [businessType 支付业务类型, request, response]
     */
    public String checkCode(String businessType, long userId, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 根据 businessType 获取 相关参数
        AdvancePaymentProperties advancePaymentProperties = advancePaymentPropertiesConfig.getParamsByBusinessType(ClientEnum.WECHAT.getClient(), businessType, userId);
        return WxGghUtil.getCodeByAuth(advancePaymentProperties.getWechatAppId(), request, response);
    }
    
    /**
     * @Description 通过code换取网页授权access_token、openid。code作为换取access_token的票据，每次用户授权带上的code将不一样，code只能使用一次，5分钟未被使用自动过期
     * @Author lixing
     * @Date 2021/7/6 16:01
     * @Param [businessType 支付业务类型, request, response]
     */
    public String checkAuth(String businessType, long userId, String code) throws UnsupportedEncodingException {
        // 根据 businessType 获取 相关参数
        AdvancePaymentProperties advancePaymentProperties = advancePaymentPropertiesConfig.getParamsByBusinessType(ClientEnum.WECHAT.getClient(), businessType, userId);
        BaseResult<JSONObject> BaseResult = WxGghUtil.getAccessTokenForAuthByCode(advancePaymentProperties.getWechatAppId(), advancePaymentProperties.getWechatAppSecret(), code);
        if (BaseResult.isSuccess()) {
            return BaseResult.getData().getString("openid");
        }
        return null;
    }

    /**
     * @Description 发起微信统一支付
     * @Author lixing
     * @Date 2021/7/6 16:30
     * @Param [client 扫码终端类型, businessType 支付业务类型]
     */
    public void weChatUnifiedorder(String client, String businessType, String openId, long userId, String orderNo, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 根据 businessType 获取 相关参数
        AdvancePaymentProperties advancePaymentProperties = advancePaymentPropertiesConfig.getParamsByBusinessType(client, businessType, userId);
        advancePaymentProperties.setWechatOpenId(openId);
        advancePaymentProperties.setUserId(userId);
        advancePaymentProperties.setOrderNo(MessageFormat.format("{0}_{1}", businessType, orderNo));
        // 发起统一支付
        Map<String, String> resultMap = WxPayUtil.unifiedOrderForAggregatePayment(request, advancePaymentProperties);
        if (WXPayConstants.SUCCESS.equals(resultMap.get("return_code")) && WXPayConstants.SUCCESS.equals(resultMap.get("result_code"))) {
            String redirectUrl = MessageFormat.format("{0}/{1}/{2}/{3}", aggregatePayWxJsapiTransferPaymentPage, businessType, String.valueOf(userId), resultMap.get("prepay_id"));
            response.sendRedirect(redirectUrl);
            /*
             * 异步添加预支付记录
             */
            applicationContext.publishEvent(new PayEvent(this, advancePaymentProperties));
        } else {
            BaseResult.outputJson(response, new BaseResult<String>().fail(resultMap.get("return_msg")));
        }
    }

    /**
     * @Description 跳转到H5页面，通过jssdk唤起微信支付
     * @Author lixing
     * @Date 2021/7/7 14:02
     * @Param [businessType 支付的业务类型, prepayId 微信统一支付后的凭证]
     * @return org.springframework.web.servlet.ModelAndView
     */
    @GetMapping("wxJsapiTransferPaymentPage/{businessType}/{userId}/{prepayId}")
    public ModelAndView wxJsapiTransferPaymentPage(@PathVariable String businessType, @PathVariable long userId, @PathVariable String prepayId) throws Exception {
        // 根据 businessType 获取 相关参数
        AdvancePaymentProperties advancePaymentProperties = advancePaymentPropertiesConfig.getParamsByBusinessType(ClientEnum.WECHAT.getClient(), businessType, userId);
        //
        Map<String, String> jsapiTransferPaymentParam = WxPayUtil.getJsapiTransferPaymentParamForAggregatePayment(prepayId, advancePaymentProperties);
        ModelAndView modelAndView = new ModelAndView("wxJsapiTransferPaymentPage");
        modelAndView.addObject("wxJsapiTransferPaymentPage",jsapiTransferPaymentParam);
        return modelAndView;
    }

}
